#include <windows.h>
#include <uxtheme.h>
#include <vsstyle.h>

#include "monitors.h"
#include "window.h"
#include "support.h"
#include "system.h"

#define BarClass "MonMan Toolbar"
#define BarHeight 32
#define BtnWidth 160

static void update_icons(HWND);
static void register_class(void);
static HWND create_tbar(HWND, struct Monitor*);
static HWND create_rebar(HWND);
static void add_to_rebar(HWND, HWND);
static void set_state(HWND, int, int);
static HWND by_index(HWND tbar, int index);

static LRESULT CALLBACK wnd_proc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp);

void init_mmbar(void)
{
	register_class();
}

void create_mmbar(struct Monitor *mon)
{
	HWND tbar, rebar, wnd;
	MONITORINFOEX *minfo = &(mon->info);
	int top = minfo->rcWork.bottom - BarHeight;
	int left = minfo->rcWork.left;
	int width = minfo->rcWork.right - left;
	
	wnd = CreateWindowEx(
		WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
		BarClass,
		"MonMan Toolbar",
		WS_POPUP | WS_VISIBLE,
		left, top,
		width, BarHeight,
		HWND_DESKTOP,
		0,
		GetModuleHandle(0),
		0
	);
	
	if(!wnd) fatal("Can't create window");
	mon->bar = wnd;
	link_to_monitor(wnd, mon);
	
	rebar = create_rebar(wnd);
	tbar = create_tbar(rebar, mon);
	add_to_rebar(rebar, tbar);
	
	SetWindowTheme(rebar, L"TaskBar", 0);
	SetWindowTheme(tbar, L"TaskBand", 0);
	
	InvalidateRect(wnd, 0, TRUE);
	
	minfo->rcWork.bottom -= BarHeight;
	//minfo->rcWork.bottom = minfo->rcMonitor.bottom;
	SystemParametersInfo(
		SPI_SETWORKAREA, 1, &minfo->rcWork, SPIF_SENDCHANGE
	);
}

void add_button(HWND tbar, HWND wnd)
{
	TBBUTTON butt;
	TBBUTTONINFO info;
	HICON icon;
	HIMAGELIST icons;
	int sz, index, img_id, i;
	char *title;
	
	if(!tbar) return;
	
	index = by_data(tbar, wnd);
	if(index != -1)
	{
		SendMessage(tbar, TB_GETBUTTON, index, (LPARAM)&butt);
		
		info.cbSize = sizeof(TBBUTTONINFO);
		info.dwMask = TBIF_TEXT;
		info.pszText = alloc_title(wnd);
		
		SendMessage(tbar, TB_SETBUTTONINFO, butt.idCommand, (LPARAM)&info);
		free(info.pszText);
		return;
	}
	
	title = alloc_title(wnd);
	
	// Find the lowest unused command
	for(i = 0;;++i)
	{
		if(SendMessage(tbar, TB_COMMANDTOINDEX, i, 0) == -1)
			break;
	}
	
	butt.idCommand = i;
	butt.fsState = TBSTATE_ENABLED;
	butt.fsStyle = 0;
	butt.dwData = (DWORD_PTR)wnd;
	butt.iString = (INT_PTR)title;
	
	SendMessage(tbar, TB_ADDBUTTONS, 1, (LPARAM)&butt);
	update_icons(tbar);
	free(title);
}

void remove_button(HWND tbar, HWND wnd)
{
	int index, num_buttons, i, i_img;
	
	if(!tbar) return;
	
	index = by_data(tbar, wnd);
	if(index != -1)
	{
		SendMessage(tbar, TB_DELETEBUTTON, index, 0);
		update_icons(tbar);
	}
}

int has_data(HWND tbar, HWND data)
{
	return by_data(tbar, data) != -1;
}

int by_data(HWND tbar, HWND wnd)
{
	TBBUTTON butt;
	DWORD data = (DWORD)wnd;
	int count;
	int i;
	
	if(!tbar) return -1;
	
	count = SendMessage(tbar, TB_BUTTONCOUNT, 0, 0);
	
	for(i = 0; i < count; ++i)
	{
		SendMessage(tbar, TB_GETBUTTON, i, (LPARAM)&butt);
		if(butt.dwData == data)
			return i;
	}
	return -1;
}

void update_button_states(HWND tbar, HWND active)
{
	int count;
	int i;
	
	if(!tbar) return;
	
	count = SendMessage(tbar, TB_BUTTONCOUNT, 0, 0);
	
	for(i = 0; i < count; ++i)
	{
		HWND current = by_index(tbar, i);
		int state = current == active;
		TBBUTTON butt;
		
		SendMessage(tbar, TB_GETBUTTON, i, (LPARAM)&butt);
		
		set_state(tbar, butt.idCommand, state);
	}
}

void activate(HWND tbar, HWND wnd)
{
	update_button_states(tbar, wnd);
}

static void update_icons(HWND tbar)
{
	int count = SendMessage(tbar, TB_BUTTONCOUNT, 0, 0);
	HIMAGELIST icons = (HIMAGELIST)SendMessage(tbar, TB_GETIMAGELIST, 0, 0);
	int i;
	
	ImageList_RemoveAll(icons);
	
	for(i = 0; i < count; ++i)
	{
		TBBUTTON butt;
		HWND wnd;
		HICON icon;
		int img_id;
		
		SendMessage(tbar, TB_GETBUTTON, i, (LPARAM)&butt);
		wnd = (HWND)butt.dwData;
		
		icon = (HICON)SendMessage(wnd, WM_GETICON, 0, 0);
		if(!icon)
		{
			icon = (HICON)GetClassLongPtr(wnd, GCL_HICONSM);
		}
		
		img_id = ImageList_AddIcon(icons, icon);
		
		SendMessage(tbar, TB_CHANGEBITMAP, butt.idCommand, img_id);
	}
	
	InvalidateRect(tbar, 0, TRUE);
}

static HWND by_index(HWND tbar, int index)
{
	TBBUTTON butt;
	SendMessage(tbar, TB_GETBUTTON, index, (LPARAM)&butt);
	return (HWND)butt.dwData;
}

static void register_class(void)
{
	WNDCLASSEX wc;
	ZeroMemory(&wc, sizeof(WNDCLASSEX));
	
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.lpfnWndProc = wnd_proc;
	wc.hInstance = GetModuleHandle(0);
	wc.hCursor = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
	wc.lpszClassName = BarClass;
	
	RegisterClassEx(&wc);
}

static HWND create_rebar(HWND parent)
{
	HWND rbar;
	RECT parect;
	int pad;
	
	GetClientRect(parent, &parect);
	
	pad = IsThemeActive()? 0 : 6;
	
	rbar = CreateWindowEx(
		WS_EX_TOOLWINDOW,
		REBARCLASSNAME,
		0,
		WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | 
		RBS_VARHEIGHT | CCS_NODIVIDER | CCS_NORESIZE | RBS_BANDBORDERS |
		RBS_AUTOSIZE | RBS_DBLCLKTOGGLE | RBS_REGISTERDROP,
		0, pad, 
		parect.right, BarHeight,
		parent,
		0,
		GetModuleHandle(0),
		0
	);
	if(!rbar) fatal("Rebar error");
	
	return rbar;
}

static void add_to_rebar(HWND rbar, HWND child)
{
	REBARBANDINFO band;
	
	band.cbSize = 80;//sizeof(REBARBANDINFO);
	band.fMask = 
		RBBIM_STYLE | RBBIM_TEXT | RBBIM_CHILD | 
		RBBIM_CHILDSIZE | RBBIM_SIZE;
	band.fStyle = RBBS_CHILDEDGE;
	band.lpText = TEXT("");
	band.hwndChild = child;
	band.cyChild = BarHeight;
	band.cxMinChild = 0;
	band.cyMinChild = BarHeight;
	band.cx = 0;
	
	if(!SendMessage(rbar, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&band))
		fatal("Band error");
}

static HWND create_tbar(HWND parent, struct Monitor *mon)
{
	TBMETRICS metrics;
	
	HWND tbar = CreateWindowEx(
		0,
		TOOLBARCLASSNAME,
		0,
		WS_CHILD | TBSTYLE_LIST | TBSTYLE_WRAPABLE | TBSTYLE_TOOLTIPS |
		CCS_NODIVIDER | CCS_NORESIZE | TBSTYLE_TRANSPARENT |
		WS_CLIPCHILDREN,
		0, 0,
		0, 0,
		parent,
		0,
		GetModuleHandle(0),
		0
	);
	if(!tbar) fatal("Toolbar error");
	
	mon->tbar = tbar;
	
	metrics.cbSize = sizeof(TBMETRICS);
	metrics.dwMask = TBMF_BUTTONSPACING;
	metrics.cxButtonSpacing = 3;
	metrics.cyButtonSpacing = 3;
	
	SendMessage(tbar, TB_SETMETRICS, 0, (LPARAM)&metrics);
	SendMessage(tbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DOUBLEBUFFER | 0x340);
	SendMessage(tbar, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
	SendMessage(tbar, TB_SETBUTTONWIDTH, 0, MAKELONG(BtnWidth, BtnWidth));
	SendMessage(tbar, TB_SETDRAWTEXTFLAGS, DT_LEFT | DT_CENTER | DT_RIGHT, DT_LEFT);
	SendMessage(tbar, TB_SETIMAGELIST, 0, (LPARAM)mon->icons);
	SendMessage(tbar, TB_SETPADDING, 0, MAKELPARAM(18, 7));
	
	ShowWindow(tbar, SW_SHOW);
	link_to_monitor(tbar, mon);
	
	return tbar;
}

static void draw_bar(HWND wnd, HDC dc)
{
	HTHEME theme = OpenThemeData(wnd, L"Taskbar");
	RECT rect;
	
	if(!theme)
		return;
	
	GetClientRect(wnd, &rect);
	DrawThemeBackground(theme, dc, 1, 1, &rect, 0);
	CloseThemeData(theme);
}

static void set_state(HWND tbar, int id, int checked)
{
	SendMessage(tbar, TB_CHECKBUTTON, id, MAKELONG(checked, 0));
}

static void left_click(HWND tbar, int cmd)
{
	int index = SendMessage(tbar, TB_COMMANDTOINDEX, cmd, 0);
	HWND wnd = by_index(tbar, index);
	
	if(!IsWindow(wnd))
	{
		printf("oops %x\n", wnd);
		remove_button(tbar, wnd);
		return;
	}
	
	bypass_hooks(1);
	if(0 && GetKeyState(VK_CONTROL) & 0x8000)
	{
		// TODO
	}
	else
	{
		if(SendMessage(tbar, TB_ISBUTTONCHECKED, cmd, 0))
		{
			ShowWindowAsync(wnd, SW_MINIMIZE);
			update_button_states(tbar, 0);
		}
		else 
		{
			if(IsIconic(wnd) || !IsWindowVisible(wnd))
			{
				ShowWindow(wnd, SW_RESTORE);
			}
			SwitchToThisWindow(wnd, FALSE);
			
			update_button_states(tbar, wnd);
		}
	}
	bypass_hooks(0);
}

static void right_click(HWND tbar, int id)
{
	int item = SendMessage(tbar, TB_COMMANDTOINDEX, id, 0);
	
	TBBUTTON btn;
	HWND target;
	HMENU menu;
	POINT mouse;
	int cmd;
	int i;
	
	GetCursorPos(&mouse);
	
	SendMessage(tbar, TB_GETBUTTON, item, (LPARAM)&btn);
	target = (HWND)btn.dwData;
	
	if(IsWindowVisible(target) && !IsIconic(target))
	{
		SetForegroundWindow(target);
	}
	
	update_button_states(tbar, target);
	
	/*
		This undocumented message causes a window to display its system menu.
	*/
	SendMessage(
		target, 
		0x313, 
		0,
		MAKELONG(mouse.x, mouse.y)
	);
}

static LRESULT CALLBACK wnd_proc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
{
	switch(msg)
	{
		case WM_ACTIVATE:
		{
			if(wp)
			{
				/*
					Toolbar was clicked while not focused. Find button under 
					cursor and activate it.
				*/
				int result;
				POINT cursor_pos;
				struct Monitor *mon = get_linked_monitor(wnd);
				HWND tbar;
				
				if(!mon)
					break;
				
				tbar = mon->tbar;
				
				GetCursorPos(&cursor_pos);
				ScreenToClient(tbar, &cursor_pos);
				result = SendMessage(tbar, TB_HITTEST, 0, (LPARAM)&cursor_pos);
				if(result >= 0)
				{
					left_click(tbar, result);
				}
			}
			break;
		}
		case WM_NOTIFY:
		{
			/*
				Toolbar was clicked while focused.
			*/
			NMHDR *nm = (NMHDR*)lp;
			HWND tbar = get_linked_monitor(wnd)->tbar;
			if(nm->hwndFrom == tbar)
			{
				int item = ((NMMOUSE*)nm)->dwItemSpec;
				
				if(nm->code == NM_RCLICK)
				{
					right_click(tbar, item);
					return 1;
				}
				else if(nm->code == NM_LDOWN)
				{
					left_click(tbar, item);
					return 1;
				}
			}
			break;
		}
		
		case WM_COMMAND:
			return 1;
		
		case WM_SIZE:
			RedrawWindow(wnd, 0, 0,
				RDW_ERASE | RDW_INVALIDATE |
				RDW_ERASENOW | RDW_ALLCHILDREN);
			break;
			
		case WM_ERASEBKGND:
			draw_bar(wnd, (HDC)wp);
			break;
			
		case WM_DESTROY:
		{
			struct Monitor *mon = get_linked_monitor(wnd);
			mon->info.rcWork.bottom += BarHeight;
			SystemParametersInfo(
				SPI_SETWORKAREA, 
				1, &mon->info.rcWork, 
				SPIF_SENDCHANGE
			);
			break;
		}
		
		case WM_CLOSE:
		{
			DestroyWindow(wnd);
			break;
		}
		
		case WM_CONTEXTMENU:
		{
			popup_menu();
			break;
		}
			
		default:
			return DefWindowProc(wnd, msg, wp, lp);
	}
	return 0;
}

